package com.suyafei.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.suyafei.enums.BizCodeEnum;
import com.suyafei.enums.CouponCategoryEnum;
import com.suyafei.enums.CouponPublishEnum;
import com.suyafei.enums.CouponStateEnum;
import com.suyafei.exception.BizException;
import com.suyafei.interceptor.LoginIntercept;
import com.suyafei.mapper.CouponMapper;
import com.suyafei.mapper.CouponRecordMapper;
import com.suyafei.model.CouponDO;
import com.suyafei.model.CouponRecordDO;
import com.suyafei.model.LoginUser;
import com.suyafei.request.NewUserCouponRequest;
import com.suyafei.service.CouponService;
import com.suyafei.util.CommonUtil;
import com.suyafei.util.JsonData;
import com.suyafei.vo.CouponVO;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @program: suyafei-1024-shop
 * @description
 * @author: luffy
 * @create: 2021-03-03 15:18
 */
@Service
@Slf4j
public class CouponServiceImpl implements CouponService {
    @Autowired
    private CouponMapper couponMapper;
    @Autowired
    private CouponRecordMapper couponRecordMapper;
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 分页查询优惠券
     *
     * @param page
     * @param size
     * @return
     */

    @Override
    public Map<String, Object> pageCouponActivity(int page, int size) {
        Page<CouponDO> pageInfo = new Page<>(page, size);
        IPage<CouponDO> couponDOIPage = couponMapper.selectPage(pageInfo, new QueryWrapper<CouponDO>()
                .eq("publish", CouponPublishEnum.PUBLISH)
                .eq("category", CouponCategoryEnum.PROMOTION)
                .orderByDesc("create_time"));

        Map<String, Object> pageMap = new HashMap<>();

        pageMap.put("total_record", couponDOIPage.getTotal());
        pageMap.put("total_page", couponDOIPage.getPages());
        pageMap.put("current_data",couponDOIPage.getRecords().stream().map(obj->beanProcess(obj)).collect(Collectors.toList()));
        return pageMap;
    }

    /**
     * 拉新服务
     * @param newUserCouponRequest
     * @return
     */
    @Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRED)
    @Override
    public JsonData addNewUserCoupon(NewUserCouponRequest newUserCouponRequest) {
        LoginUser loginUser = new LoginUser();
        loginUser.setId(newUserCouponRequest.getUserId());
        loginUser.setName(newUserCouponRequest.getName());
        LoginIntercept.threadLocal.set(loginUser);

        //查询新用户有哪些优惠券
        List<CouponDO> couponDOList = couponMapper.selectList(new QueryWrapper<CouponDO>()
                .eq("category",CouponCategoryEnum.NEW_USER.name()));

        for(CouponDO couponDO : couponDOList){
            //幂等操作，调用需要加锁
            this.addCoupon(couponDO.getId(),CouponCategoryEnum.NEW_USER);

        }

        return JsonData.buildSuccess();
    }

    /**
     * 领取优惠券
     *
     * 1.获取优惠券是否存在
     * 2.校验优惠券是否可以领取：时间、库存、超过限制
     * 3、扣减库存
     * 4、保存领劵记录
     * @param couponId
     * @param category
     * @return
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED )
    @Override
    public JsonData addCoupon(long couponId, CouponCategoryEnum category) {

        String lockKey = "lock:coupon:" + couponId;
        RLock rlock = redissonClient.getLock(lockKey);
        //加锁，多个线程进入，会阻塞等待释放锁
        rlock.lock();
        try{
            //加锁成功
            log.info("加锁成功：{}",Thread.currentThread().getId());
            //执行业务逻辑
            LoginUser loginUser = LoginIntercept.threadLocal.get();

            CouponDO couponDO = couponMapper.selectOne(new QueryWrapper<CouponDO>()
                    .eq("id", couponId)
                    .eq("category", category.name()));

            if (couponDO == null) {
                throw new BizException(BizCodeEnum.COUPON_NO_EXITS);
            }
            //优惠券是否可以领取
            this.checkCoupon(couponDO, loginUser.getId());
            //构建领劵记录
            CouponRecordDO couponRecordDO = new CouponRecordDO();
            BeanUtils.copyProperties(couponDO, couponRecordDO);
            couponRecordDO.setCreateTime(new Date());
            couponRecordDO.setUseState(CouponStateEnum.NEW.name());
            couponRecordDO.setUserId(loginUser.getId());
            couponRecordDO.setUserName(loginUser.getName());
            couponRecordDO.setCouponId(couponId);
            couponRecordDO.setId(null);

            //扣减库存
            int rows = couponMapper.reduceStock(couponId);

            if (rows == 1) {
                couponRecordMapper.insert(couponRecordDO);
            }else {
                log.warn("发放优惠券失败：{},用户：{}", couponDO, loginUser.getName());
                throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
            }
        }finally {
            rlock.unlock();
            log.info("解锁:{}",Thread.currentThread().getId());
        }


        return JsonData.buildSuccess("成功");
    }

    /**
     * 校验是否可以领取优惠券
     * @param couponDO
     * @param userId
     */
    private void checkCoupon(CouponDO couponDO, Long userId) {

        //库存是否足够
        if (couponDO.getStock() <= 0) {
            throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
        }
        //判断是否是发布状态
        if (!couponDO.getPublish().equals(CouponPublishEnum.PUBLISH.name())) {
            throw new BizException(BizCodeEnum.COUPON_GET_FAIL);
        }

        //是否在领取时间范围内
        long currentTimestamp = CommonUtil.getCurrentTimestamp();
        long startTime = couponDO.getStartTime().getTime();
        long endTime = couponDO.getEndTime().getTime();
        if (currentTimestamp < startTime || currentTimestamp > endTime) {
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_TIME);
        }

        //用户是否超过限制
        int recordNum = couponRecordMapper.selectCount(new QueryWrapper<CouponRecordDO>()
                .eq("coupon_id", couponDO.getId())
                .eq("user_id", userId));
        if (recordNum >= couponDO.getUserLimit()) {
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_LIMIT);
        }
    }

    /**
     * DO 转 VO
     * @param obj
     * @return
     */
    private CouponVO beanProcess(CouponDO obj) {
        CouponVO couponVO = new CouponVO();
        BeanUtils.copyProperties(obj, couponVO);
        return couponVO;
    }
}
